﻿using DivaScript;
using DivaScriptConv;
using ft_module_parser.aftconversion;
using ft_module_parser.aftconversion.divax;
using MikuMikuModel.FarcPack;
using MikuMikuModel.Logs;
using ScriptUtilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static ft_module_parser.aftconversion.divax.pvparam_lighttest;

namespace ft_module_parser.fconversion.divax
{
    class dsc
    {
        public static void ConvertDSC(string path, string acpath, divamodgen divamods, bool skippvparam, ConcurrentStack<PVPPTransition> pvpp, bool skip = false, bool from_cache = false)
        {
            Parallel.ForEach(Directory.EnumerateFiles(path, "pv_*_common.farc", SearchOption.TopDirectoryOnly), new ParallelOptions { MaxDegreeOfParallelism = 3 }, file =>
            {
                Tools.Extract(file, "dsc\\common\\");
            });

            Parallel.ForEach(Directory.EnumerateFiles(path, "*.dsc", SearchOption.TopDirectoryOnly), new ParallelOptions { MaxDegreeOfParallelism = 3 }, file =>
            {
                File.Copy(file, "dsc\\" + Path.GetFileName(file), true);
            });

            Parallel.ForEach(Directory.EnumerateFiles("dsc\\", "*.dsc", SearchOption.TopDirectoryOnly), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, file =>
            {
                PD_Tool.DIVAFILE.Decrypt(0, file);

            });

            //PVPPProcess(acpath, pvpp);
            //var pvpplist = pvpp.ToList();

            pvparam_simple.PVPPProcess(acpath);

            //Parallel.ForEach(Directory.EnumerateFiles("dsc\\common\\", "*.dsc", SearchOption.TopDirectoryOnly), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, file =>
            //{
            //    var test = PvScript.ParseBinaryScripts(new string[] { file }, Format.X, false);
            //    PvScript.WriteTextScript(file + ".txt", test);
            //});

            Console.Title = "LYB DIVA X";

            if (File.Exists("x_dsc_cache"))
            {
                File.Delete("x_dsc_cache");
            }

            foreach (string file in Directory.EnumerateFiles("dsc//", "*.dsc", SearchOption.TopDirectoryOnly))
            {
                int pvid = int.Parse(Path.GetFileNameWithoutExtension(file).Substring(3, 3));
                var check = divamods.Divamods.Where(c => c.pvid == pvid).FirstOrDefault();

                if (check == null)
                {
                    divamods.Divamods.Add(new aftconversion.divamods(pvid));
                    Logs.WriteLine("DSC: Created new PV at id " + (pvid));
                    check = divamods.Divamods.Where(c => c.pvid == pvid).First();
                }

                if (Path.GetFileName(file).Contains("easy"))
                    check.easy = true;
                if (Path.GetFileName(file).Contains("normal"))
                    check.normal = true;
                if (Path.GetFileName(file).Contains("hard"))
                    check.hard = true;
                if (Path.GetFileName(file).Contains("extreme"))
                    check.extreme = true;
            }

            String[] str;
            str = File.ReadAllLines("x_generic.csv");

            if (!skip)
                Parallel.ForEach(Directory.EnumerateFiles("dsc\\", "*.*", SearchOption.TopDirectoryOnly), new ParallelOptions { MaxDegreeOfParallelism = 1 }, file =>
                {
                    Logs.WriteLine("DSC: Converting " + (file));
                    List<string> args = new List<string>();
                    args.Add("-i:x");
                    args.Add(file);
                    args.Add("-o:a");
                    args.Add(acpath + @"\rom\script\" + Path.GetFileNameWithoutExtension(file) + "_divax.dsc");

                    int pvid = int.Parse(Path.GetFileNameWithoutExtension(file).Substring(3, 3));

                    string dexfile = path + "exp_pv" + string.Format("{0:000}", pvid) + ".dex";

                    var check = divamods.Divamods.Where(c => c.pvid == pvid).First();
                    if (File.Exists(dexfile))
                    {
                        args.Add("-e");
                        args.Add(dexfile);
                        Logs.WriteLine("PD_TOOL: DexReader: " + Path.GetFileName(dexfile), 1);

                        //File.Copy(dexfile, acpath + "\\rom\\pv_expression\\" + "exp_pv" + string.Format("{0:000}", pvid) + ".bin", true);
                        /*
                        var DEX = new KKdDEX();
                        DEX.DEXReader(path + "exp_pv" + string.Format("{0:000}", pvid +"."), "dex");
                        DEX.DEXWriter(acpath + "\\rom\\pv_expression\\" + "exp_pv" + string.Format("{0:000}", pvid), Main.Format.DT2);
                        check.pv_expr = true;
                        */
                    }

                    {
                        if (check.performers == 2)
                        {
                            Logs.WriteLine("DSC: " + pvid + " 1 perfomers", 1);
                        }

                        if (check.performers == 2)
                        {
                            Logs.WriteLine("DSC: " + pvid + " 2 perfomers", 1);
                        }

                        if (check.performers == 3)
                        {
                            Logs.WriteLine("DSC: " + pvid + " 3 perfomers", 1);
                        }
                    }

                    {
                        var pvcmds = new PvCmds();

                        var _pv = PvScript.ParseBinaryScripts(new string[] { file }, Format.X, false);
                        
                        foreach (var k in _pv.ToList())
                        {
                            if (k.Opcode == "TARGET")
                                switch (k.Parameters[0])
                                {
                                    case 25:
                                        k.Parameters[0] = 4;
                                        break;

                                    case 26:
                                        k.Parameters[0] = 5;
                                        break;

                                    case 27:
                                        k.Parameters[0] = 6;
                                        break;

                                    case 28:
                                        k.Parameters[0] = 7;
                                        break;

                                    default:
                                        break;
                                }
                        }

                        pvcmds.Load(_pv);

                        pvcmds.Insert(0, new PvCommand(0, "MUSIC_PLAY"));

                        pvcmds.Insert(0, new PvCommand(0, "DATA_CAMERA", 0, 1));
                        pvcmds.Insert(0, new PvCommand(0, "MIKU_DISP", 0, 1));
                        if (check.performers == 2)
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_DISP", 1, 1));

                        if (check.performers == 3)
                        {
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_DISP", 1, 1));
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_DISP", 2, 1));
                        }

                        pvcmds.Insert(0, new PvCommand(0, "HAND_ANIM", 0, 0, 9, -1, -1));
                        pvcmds.Insert(0, new PvCommand(0, "HAND_ANIM", 0, 1, 9, -1, -1));

                        if (check.performers == 2)
                        {
                            pvcmds.Insert(0, new PvCommand(0, "HAND_ANIM", 1, 0, 9, -1, -1));
                            pvcmds.Insert(0, new PvCommand(0, "HAND_ANIM", 1, 1, 9, -1, -1));
                        }

                        if (check.performers == 3)
                        {

                            pvcmds.Insert(0, new PvCommand(0, "HAND_ANIM", 2, 0, 9, -1, -1));
                            pvcmds.Insert(0, new PvCommand(0, "HAND_ANIM", 2, 1, 9, -1, -1));

                            pvcmds.Insert(0, new PvCommand(0, "HAND_ANIM", 1, 0, 9, -1, -1));
                            pvcmds.Insert(0, new PvCommand(0, "HAND_ANIM", 1, 1, 9, -1, -1));

                        }

                        pvcmds.Insert(0, new PvCommand(0, "SET_PLAYDATA", 0, 1));

                        if (check.performers == 2)
                        {
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_ROT", 1, 0));
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_MOVE", 1, 0, 0, 0));
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_DISP", 1, 1));
                        }

                        if (check.performers == 3)
                        {
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_ROT", 2, 0));
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_MOVE", 2, 0, 0, 0));
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_DISP", 2, 1));

                            pvcmds.Insert(0, new PvCommand(0, "MIKU_ROT", 1, 0));
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_MOVE", 1, 0, 0, 0));
                            pvcmds.Insert(0, new PvCommand(0, "MIKU_DISP", 1, 1));
                        }

                        pvcmds.Insert(0, new PvCommand(0, "SET_MOTION", 0, 1, -1, 1000));

                        if (check.performers == 2)
                        {
                            pvcmds.Insert(0, new PvCommand(0, "SET_MOTION", 1, 1, -1, 1000));
                        }

                        if (check.performers == 3)
                        {
                            pvcmds.Insert(0, new PvCommand(0, "SET_MOTION", 2, 1, -1, 1000));
                            pvcmds.Insert(0, new PvCommand(0, "SET_MOTION", 1, 1, -1, 1000));
                        }

                        pvcmds.Insert(1, new PvCommand(0, "CHANGE_FIELD", 1));

                        if (File.Exists("dsc\\common\\pv_" + pvid + "_system.dsc"))
                        {
                            var scene = PvScript.ParseBinaryScripts(new string[] { "dsc\\common\\pv_" + String.Format("{0:000}", pvid) + "_system.dsc" }, Format.X, false);
                            scene.RemoveAll(c => c.Opcode != "MODE_SELECT");
                            pvcmds.Load(scene);
                        }

                        if (File.Exists("dsc\\common\\pv_" + pvid + "_scene.dsc"))
                        {
                            var scene = PvScript.ParseBinaryScripts(new string[] { "dsc\\common\\pv_" + String.Format("{0:000}", pvid) + "_scene.dsc" }, Format.X, false);
                            int last_stage = -1;
                            foreach (var c in scene)
                            {
                                switch (c.Opcode)
                                {
                                    case "CHANGE_FIELD":
                                        var type = 1;
                                        foreach (var cc in str)
                                        {
                                            var lines = cc.Split(',');
                                            var fieldid = int.Parse(lines[0]);
                                            var fieldtype = int.Parse(lines[1]);
                                            var param = c.Parameters[0];

                                            if (param > fieldid)
                                                type = fieldtype;
                                        }
                                        if (type != 1)
                                            pvcmds.Insert(new PvCommand(0, "CHANGE_FIELD", new int[] { type }), c.TimeStamp);
                                        break;

                                    default:
                                        break;
                                }
                            }
                        }

                        if (File.Exists("dsc\\common\\pv_" + pvid + "_mouth.dsc"))
                        {
                            var scene = PvScript.ParseBinaryScripts(new string[] { "dsc\\common\\pv_" + String.Format("{0:000}", pvid) + "_mouth.dsc" }, Format.X, false);
                            scene.RemoveAll(c => c.Opcode != "MOUTH_ANIM");
                            foreach (var k in scene.ToList())
                            {
                                /*
                                if (k.Parameters[2] == 14)
                                {
                                    k.Parameters[2] = 28;
                                    k.Parameters[3] = 100;
                                    k.Parameters[4] = 1000;
                                }
                                */
                                switch (k.Parameters[2])
                                {
                                    case 5:
                                        k.Parameters[2] = 5;
                                        break;

                                    case 6:
                                        k.Parameters[2] = 2;
                                        break;

                                    case 8:
                                        k.Parameters[2] = 38;
                                        break;

                                    case 11:
                                        k.Parameters[2] = 5;
                                        break;

                                    case 12:
                                        k.Parameters[2] = 13;
                                        break;

                                    case 13:
                                        k.Parameters[2] = 12;
                                        break;

                                    case 14:
                                        k.Parameters[2] = 8;
                                        break;

                                    case 15:
                                        k.Parameters[2] = 9;
                                        break;

                                    case 16:
                                        k.Parameters[2] = 10;
                                        break;

                                    case 19:
                                        k.Parameters[2] = 6;
                                        break;

                                    case 17:
                                        k.Parameters[2] = 11;
                                        break;

                                    case 4:
                                        k.Parameters[2] = 16;
                                        break;

                                    case 22:
                                        k.Parameters[2] = 16;
                                        break;

                                    default:
                                        break;
                                }

                            }
                            pvcmds.Load(scene);
                        }

                        pvcmds.Insert(new PvCommand(0, "PV_END"), pvcmds.pvCommands.OrderBy(c => c.Time).Last().Time - 1000);

                        if (File.Exists(acpath + @"\rom\pv_expression\exp_PV" + String.Format("{0:000}", pvid) + ".bin"))
                        {
                            //check.pv_expr = true;
                            pvcmds = SyncExpression(pvcmds, dexfile, pvcmds.pvCommands.OrderBy(c => c.Time).Last().Time, true, true);
                        }
                        else
                        {
                            pvcmds = SyncExpression(pvcmds, dexfile, pvcmds.pvCommands.OrderBy(c => c.Time).Last().Time);
                        }


                        if (!Directory.Exists(acpath + @"\rom\script\pv" + pvid + @"\"))
                            Directory.CreateDirectory(acpath + @"\rom\script\pv" + pvid);
                        PvScript.WriteBinaryScript(acpath + @"\rom\script\pv" + pvid + @"\" + Path.GetFileNameWithoutExtension(file) + ".dsc", pvcmds.Export(), Format.F);
                    }
                    //DSC.Convert(args.ToArray(), check.performers, true);
                });


        }

        static PvCmds SyncExpression(PvCmds pvcmd, string PvExpression, int last = int.MaxValue, bool eye = true, bool main = true)
        {
            try
            {
                var dex = new DexFactory(PvExpression);
                if (dex.ReadDEX() != 1) return pvcmd; // Error when reading DEX
                if (dex.DEX.Length == 0) return pvcmd; // Has no DEX data
                int lp, ll;
                // TODO : SyncExpression / Not supported yet
                lp = 0;

                for (int d = 0; d < dex.DEX.Length; d++)
                {
                    var h = 0;

                    if (dex.DEX[d].Name.Contains("P2"))
                        h = 1;

                    if (dex.DEX[d].Name.Contains("P3"))
                        h = 2;

                    if (main)
                        foreach (var e in dex.DEX[d].Main)
                        {
                            var te = (int)Math.Round(e.Frame * 100000 / 60, 0);
                            //var h = int.Parse(dex.DEX[d].Name.Substring(1, 1));
                            var ea = new PvCommand(te, "EXPRESSION", h, e.ID, (int)Math.Round((e.Value * 1000), 0), -1);
                            if (te < last)
                                pvcmd.Insert(ea, te);
                            else
                            {
                                Logs.WriteLine("DSC : Skipped 1 exp at " + te, 1);
                            }
                        }
                    if (eye)
                        foreach (var e in dex.DEX[d].Eyes)
                        {
                            var te = (int)Math.Round(e.Frame * 100000 / 60, 0);
                            //var h = int.Parse(dex.DEX[d].Name.Substring(1, 1));
                            var ea = new PvCommand(te, "EYE_ANIM", h, 2 - (int)Math.Round(e.Value, 0), -1);
                            if (te < last)
                                pvcmd.Insert(ea, te);
                            else
                            {
                                Logs.WriteLine("DSC : Skipped 1 exp at " + te, 1);
                            }
                        }
                }

            }
            catch (Exception ee)
            {
                Logs.WriteLine("DSC : Exception " + ee.Message + "\n" + ee.StackTrace);
            }
            return pvcmd;
        }
    }

}
